﻿using System.Collections.Concurrent;
using System.Linq.Dynamic.Core;
using System.Linq.Expressions;

using ApproveFlowEngine.EntityFrameworkCore;
using ApproveFlowEngine.EntityFrameworkCoreSplitTable.EFCore.Core;
using ApproveFlowEngine.EntityFrameworkCoreSplitTable.Tools;
using ApproveFlowEngine.Split;

using Microsoft.EntityFrameworkCore;

using Volo.Abp.Caching;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Entities;
using Volo.Abp.Domain.Repositories.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore;

namespace ApproveFlowEngine.EntityFrameworkCoreSplitTable.AbpCore
{
    public class SplitRepository<TDbContext, TEntity> : EfCoreRepository<TDbContext, TEntity>, ISplitRepository<TEntity> where TDbContext : SplitDbContext where TEntity : class, IEntity, ISplitTable
    {
        #region Private Fields

        private readonly IDistributedCache<List<string>> _cache;
        private readonly ICachedServiceProvider _cachedServiceProvider;
        private readonly ISplitTableManager<TDbContext> _tableCreator;

        #endregion Private Fields

        #region Public Constructors

        public SplitRepository(IDbContextProvider<TDbContext> dbContextProvider, ICachedServiceProvider cachedServiceProvider, IDistributedCache<List<string>> cache, ISplitTableManager<TDbContext> tableCreator) : base(dbContextProvider)
        {
            _cachedServiceProvider = cachedServiceProvider;
            _cache = cache;
            _tableCreator = tableCreator;
        }

        #endregion Public Constructors



        #region Public Methods

        public override async Task DeleteAsync(TEntity entity, bool autoSave = false, CancellationToken cancellationToken = default)
        {
            var tail = await _tableCreator.GetSplitTableTail(entity);
            if (string.IsNullOrEmpty(tail))
            {
                return;
            }
            TableTailHelper.SetTail(entity.GetType(), entity.GetTail());
            await base.DeleteAsync(entity, autoSave, cancellationToken);
        }

        public override async Task DeleteManyAsync(IEnumerable<TEntity> entities, bool autoSave = false, CancellationToken cancellationToken = default)
        {
            var entityDic = BuildEntities(entities);
            var validTails = await _tableCreator.GetValidTails<TEntity>(entityDic.Keys);
            await Parallel.ForEachAsync(validTails, async (tail, token) =>
            {
                TableTailHelper.SetTail<TEntity>(tail);
                await base.DeleteManyAsync(entityDic[tail], autoSave, cancellationToken);
            });
        }

        public async Task<long> GetCountAsync(DateTime current, CancellationToken cancellationToken = default)
        {
            var tail = await _tableCreator.GetSplitTableTail<TEntity>(current);
            if (string.IsNullOrEmpty(tail))
            {
                return 0;
            }
            TableTailHelper.SetTail<TEntity>(tail);
            return await base.GetCountAsync(cancellationToken);
        }

        public async Task<long> GetCountAsync(DateTime? begin = null, DateTime? end = null, CancellationToken cancellationToken = default)
        {
            var resultCount = new ConcurrentBag<long>();
            var tails = await _tableCreator.GetSplitTableTails<TEntity>(begin, end);

            await Parallel.ForEachAsync(tails, async (tail, token) =>
            {
                TableTailHelper.SetTail<TEntity>(tail);
                var count = await base.GetCountAsync(cancellationToken);
            });
            return resultCount.Sum();
        }

        public async Task<List<TEntity>> GetListAsync(Expression<Func<TEntity, bool>> predicate, DateTime? begin = null, DateTime? end = null, bool includeDetails = false, CancellationToken cancellationToken = default)
        {
            var bag = new ConcurrentBag<List<TEntity>>();
            var validateTableTails = await _tableCreator.GetSplitTableTails<TEntity>(begin, end);

            await Parallel.ForEachAsync(validateTableTails, async (tail, token) =>
            {
                TableTailHelper.SetTail<TEntity>(tail);
                var tempData = await base.GetListAsync(predicate, includeDetails, cancellationToken);
                bag.Add(tempData);
            });

            var result = new List<TEntity>();
            foreach (var item in bag)
            {
                result.AddRange(item);
            }
            return result;
        }

        public async Task<List<TEntity>> GetPagedListAsync<TOrder>(Expression<Func<TEntity, TOrder>> keySelector, int skipCount, int maxResultCount, DateTime? begin = null, DateTime? end = null, bool includeDetails = false, CancellationToken cancellationToken = default)
        {
            return await GetPagedListAsync(c => true, keySelector, skipCount, maxResultCount, begin, end, includeDetails, cancellationToken);
        }

        /// <summary>
        /// 二次查表法
        /// </summary>
        /// <param name="tails"> </param>
        /// <param name="skipCount"> </param>
        /// <param name="maxResultCount"> </param>
        /// <param name="sorting"> </param>
        /// <param name="includeDetails"> </param>
        /// <param name="cancellationToken"> </param>
        /// <returns> </returns>
        public async Task<List<TEntity>> GetPagedListAsync<TOrder>(Expression<Func<TEntity, bool>> predicate, Expression<Func<TEntity, TOrder>> keySelector, int skipCount, int maxResultCount, DateTime? begin = null, DateTime? end = null, bool includeDetails = false, CancellationToken cancellationToken = default)
        {
            if (keySelector.Body is not MemberExpression member)
            {
                throw new Exception("排序参数错误！");
            }

            var validateTails = await _tableCreator.GetSplitTableTails<TEntity>(begin, end);
            var entityType = typeof(TEntity);

            // 跳过总行数拆分到每个表向下取整
            var onSkipCount = skipCount / validateTails.Count;
            // 拆分总行数偏移量
            var splitOffset = skipCount - (onSkipCount * validateTails.Count);

            //第一次查询找出最小值
            var oneDataDic = new ConcurrentDictionary<string, List<TEntity>>();
            await Parallel.ForEachAsync(validateTails, cancellationToken, async (tail, token) =>
            {
                TableTailHelper.SetTail<TEntity>(tail);

                var query = await GetQueryableAsync().ConfigureAwait(continueOnCapturedContext: false);
                var tempData = await query.Where(predicate).OrderBy(keySelector).Skip(onSkipCount).Take(maxResultCount).ToListAsync(token);
                if (tempData.Any())
                {
                    oneDataDic.TryAdd(tail, tempData);
                }
            });

            //找出最小值
            var oneData = oneDataDic.SelectMany(c => c.Value).ToList();
            var minEntity = oneData.OrderBy(keySelector.Compile()).First();
            var min = GetProsperityValue<TOrder>(minEntity, member.Member.Name);

            //第二次查询得到分页数据
            var twoDataBag = new ConcurrentBag<List<TEntity>>();
            //查询为空偏移量
            var emptyOffset = 0;
            //查询不为空偏移量
            var noEmptyOffset = 0;

            await Parallel.ForEachAsync(validateTails, cancellationToken, async (tail, token) =>
            {
                TableTailHelper.SetTail<TEntity>(tail);
                // 上一次查询有返回数据，查询上一次返回所有表最小值和当前表最大值之间的数据，新偏移量=返回总数-上次查询返回总数
                if (oneDataDic.TryGetValue(tail, out var entities))
                {
                    var max = GetProsperityValue<TOrder>(entities.Last(), member.Member.Name);

                    var query = !includeDetails ? await GetQueryableAsync().ConfigureAwait(continueOnCapturedContext: false) : await WithDetailsAsync().ConfigureAwait(continueOnCapturedContext: false);
                    // 构建查询表达式 min < xx < max
                    var queryExpression = BuildQueryExpression(member, min, max);
                    var tempData = await query.Where(predicate).Where(queryExpression).OrderBy(keySelector).ToListAsync(token);

                    twoDataBag.Add(tempData);
                }
                // 上一次查询未返回数据，说明可能存在数据不足，查询当前表总条数计算偏移量，新偏移量=分表偏移量-返回总数
                else
                {
                    var query = await GetQueryableAsync().ConfigureAwait(continueOnCapturedContext: false);
                    var practicalCount = await query.Where(predicate).CountAsync();
                    var skip = onSkipCount - practicalCount;
                    Interlocked.Add(ref emptyOffset, skip);
                }
            });

            var twoData = twoDataBag.SelectMany(c => c).ToList();
            noEmptyOffset = twoData.Count - oneData.Count;
            // 计算偏移量
            var minOffset = splitOffset + noEmptyOffset + emptyOffset;

            // 组装数据
            var result = twoData.OrderBy(keySelector.Compile()).Skip(minOffset).Take(maxResultCount).ToList();
            return result;
        }

        public async Task<IQueryable<TEntity>> GetQueryableAsync(DateTime current)
        {
            var tail = await _tableCreator.GetSplitTableTail<TEntity>(current);
            if (string.IsNullOrEmpty(tail))
            {
                throw new Exception($"【{current}】时间错误，当前时间不存在分表！");
            }
            TableTailHelper.SetTail<TEntity>(tail);
            return await GetQueryableAsync();
        }

        public override async Task<TEntity> InsertAsync(TEntity entity, bool autoSave = false, CancellationToken cancellationToken = default)
        {
            TableTailHelper.SetTail(entity.GetType(), entity.GetTail());
            await _tableCreator.CheckAndCreateTable<TEntity>();
            return await base.InsertAsync(entity, autoSave, cancellationToken);
        }

        public override async Task InsertManyAsync(IEnumerable<TEntity> entities, bool autoSave = false, CancellationToken cancellationToken = default)
        {
            var entityDic = BuildEntities(entities);
            await Parallel.ForEachAsync(entityDic, cancellationToken, async (item, token) =>
            {
                TableTailHelper.SetTail<TEntity>(item.Key);
                await _tableCreator.CheckAndCreateTable<TEntity>();
                await base.InsertManyAsync(item.Value, autoSave, token);
            });
        }

        public override async Task<TEntity> UpdateAsync(TEntity entity, bool autoSave = false, CancellationToken cancellationToken = default)
        {
            TableTailHelper.SetTail(entity.GetType(), entity.GetTail());
            await _tableCreator.CheckAndCreateTable<TEntity>();
            return await base.UpdateAsync(entity, autoSave, cancellationToken);
        }

        public override async Task UpdateManyAsync(IEnumerable<TEntity> entities, bool autoSave = false, CancellationToken cancellationToken = default)
        {
            var entityDic = BuildEntities(entities);
            await Parallel.ForEachAsync(entityDic, cancellationToken, async (item, token) =>
            {
                TableTailHelper.SetTail<TEntity>(item.Key);
                await base.UpdateManyAsync(entities, autoSave, cancellationToken);
            });
        }

        #endregion Public Methods



        #region Private Methods

        private Dictionary<string, List<TEntity>> BuildEntities(IEnumerable<TEntity> entities)
        {
            var entityDic = new Dictionary<string, List<TEntity>>();
            foreach (var entity in entities)
            {
                var tail = entity.GetTail();
                if (entityDic.ContainsKey(tail))
                {
                    entityDic[tail].Add(entity);
                }
                else
                {
                    entityDic.Add(tail, new List<TEntity>() { entity });
                }
            }
            return entityDic;
        }

        /// <summary>
        /// 构造 where 表达式
        /// </summary>
        /// <typeparam name="TProp"> </typeparam>
        /// <param name="member"> </param>
        /// <param name="min"> </param>
        /// <param name="max"> </param>
        /// <returns> </returns>
        private Expression<Func<TEntity, bool>> BuildQueryExpression<TProp>(MemberExpression member, TProp min, TProp max)
        {
            var parameter = Expression.Parameter(typeof(TEntity), "c");
            var orderMember = Expression.Property(parameter, member.Member.Name);
            var minConstant = Expression.Constant(min);
            var maxConstant = Expression.Constant(max);
            var less = Expression.LessThanOrEqual(orderMember, maxConstant);
            var than = Expression.GreaterThanOrEqual(orderMember, minConstant);
            var body = Expression.AndAlso(less, than);
            var query = Expression.Lambda<Func<TEntity, bool>>(body, parameter);
            return query;
        }

        private T GetProsperityValue<T>(TEntity entity, string prosperityName)
        {
            var result = default(T);
            foreach (var item in typeof(TEntity).GetProperties())
            {
                if (item.PropertyType == typeof(T) && item.Name == prosperityName)
                {
                    var propValue = item.GetValue(entity);
                    if (propValue is not null)
                    {
                        result = (T)propValue;
                        break;
                    }
                }
            }
            return result;
        }

        #endregion Private Methods
    }
}